﻿/*
 * 由SharpDevelop创建。
 * 用户： Admin
 * 日期: 2025/5/3
 * 时间: 12:35
 * 
 * 要改变这种模板请点击 工具|选项|代码编写|编辑标准头文件
 */
using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace BDSCSBox
{
	class Program : DataAdapter
	{
		private static Process myProcess = null;
		private static bool keeprun = false;
		private static string procpath = null;

		private static ArrayList dataReceivers = new ArrayList();
		private static ArrayList dataSenders = new ArrayList();
		
		private static Program i_this = null;
		
		public const string BOXCONFIGFILE = "BoxConfig.ini";
		public const string KEY_WORKDIR = "workdir";
		public const string KEY_PROPATH = "propath";
		public const string KEY_WEBDOWN = "webdown";

		public static string WORKDIR = @".\";
		public static string BDSPRONAME = "bedrock_server.exe";
		public static string WEBDOWN = "https://www.minecraft.net/bedrockdedicatedserver/bin-win/bedrock-server-1.21.124.2.zip";
		
		public const string BOXERDIR = "CSL";	// 接收器所在目录

		#region DataAdapter implementation
		public void addDataReceiver(TYPES.F_FUNC f)
		{
			if (f != null)
				dataReceivers.Add(f);
		}
		public void removeDataReceiver(TYPES.F_FUNC f)
		{
			if (f != null)
				dataReceivers.Remove(f);
		}
		public void addDataSender(TYPES.F_FUNC f)
		{
			if (f != null)
				dataSenders.Add(f);
		}
		public void removeDataSender(TYPES.F_FUNC f)
		{
			if (f != null)
				dataSenders.Remove(f);
		}
		public string sendMsg(string s)
		{
			return sendCommand(s);
		}

		public string workDir {
			get {
				return WORKDIR;
			}
		}
		
		public void loadAllLib() {		// 加载指定目录下所有CSL监听组件
			var dir = System.AppDomain.CurrentDomain.BaseDirectory + BOXERDIR;
			if (Directory.Exists(dir)) {
				var files = Directory.GetFiles(System.AppDomain.CurrentDomain.BaseDirectory + BOXERDIR, "*.dll");
				if (files != null) {
					foreach (var dll in files) {
						var dllpath = dll;
						Console.WriteLine("[BDSCSBOX] 加载监听组件 " + dllpath);
						Assembly myAssembly = Assembly.LoadFrom(dllpath);
						Type t = myAssembly.GetType("BDSCSBox.CSL");
						var minstanse = Activator.CreateInstance(t);
						var startm = t.GetMethod("onStart");
						var ret = startm.Invoke(minstanse, new DataAdapter[]{this});
					}
				}
			}
		}
		
		#endregion
			
		#region PInvokes
		[DllImport("Kernel32.dll")]
		private static extern bool SetConsoleCP(int codepage);
		[DllImport("KERNEL32.DLL")]
		private static extern int OpenProcess(eDesiredAccess dwDesiredAccess, bool bInheritHandle, int dwProcessId);
		[DllImport("KERNEL32.DLL")]
		private static extern int CloseHandle(int hObject);
		[DllImport("NTDLL.DLL")]
		private static extern int NtQueryInformationProcess(int hProcess, PROCESSINFOCLASS pic, ref PROCESS_BASIC_INFORMATION pbi, int cb, ref int pSize);
		private enum PROCESSINFOCLASS : int
		{
			ProcessBasicInformation = 0,
			ProcessQuotaLimits,
			ProcessIoCounters,
			ProcessVmCounters,
			ProcessTimes,
			ProcessBasePriority,
			ProcessRaisePriority,
			ProcessDebugPort,
			ProcessExceptionPort,
			ProcessAccessToken,
			ProcessLdtInformation,
			ProcessLdtSize,
			ProcessDefaultHardErrorMode,
			ProcessIoPortHandlers,
			// Note: this is kernel mode only
			ProcessPooledUsageAndLimits,
			ProcessWorkingSetWatch,
			ProcessUserModeIOPL,
			ProcessEnableAlignmentFaultFixup,
			ProcessPriorityClass,
			ProcessWx86Information,
			ProcessHandleCount,
			ProcessAffinityMask,
			ProcessPriorityBoost,
			MaxProcessInfoClass}

		;

		[StructLayout(LayoutKind.Sequential)]
		private struct PROCESS_BASIC_INFORMATION
		{
			public int ExitStatus;
			public int PebBaseAddress;
			public int AffinityMask;
			public int BasePriority;
			public int UniqueProcessId;
			public int InheritedFromUniqueProcessId;
			public int Size {
				get {
					return (6 * 4);
				}

			}

		};

		private enum eDesiredAccess : int
		{
			DELETE = 0x00010000,
			READ_CONTROL = 0x00020000,
			WRITE_DAC = 0x00040000,
			WRITE_OWNER = 0x00080000,
			SYNCHRONIZE = 0x00100000,
			STANDARD_RIGHTS_ALL = 0x001F0000,
			PROCESS_TERMINATE = 0x0001,
			PROCESS_CREATE_THREAD = 0x0002,
			PROCESS_SET_SESSIONID = 0x0004,
			PROCESS_VM_OPERATION = 0x0008,
			PROCESS_VM_READ = 0x0010,
			PROCESS_VM_WRITE = 0x0020,
			PROCESS_DUP_HANDLE = 0x0040,
			PROCESS_CREATE_PROCESS = 0x0080,
			PROCESS_SET_QUOTA = 0x0100,
			PROCESS_SET_INFORMATION = 0x0200,
			PROCESS_QUERY_INFORMATION = 0x0400,
			PROCESS_ALL_ACCESS = SYNCHRONIZE | 0xFFF

		}
		#endregion
		
		public static void KillTree(int processToKillId)
		{
			// Kill each child process
			foreach (int childProcessId in GetChildProcessIds(processToKillId)) {
				using (Process child = Process.GetProcessById(childProcessId)) {
					child.Kill();

				}

			}

			// Then kill this process
			using (Process thisProcess = Process.GetProcessById(processToKillId)) {
				thisProcess.Kill();
			}
		}

		public static int GetParentProcessId(int processId)
		{
			int ParentID = 0;
			int hProcess = OpenProcess(eDesiredAccess.PROCESS_QUERY_INFORMATION,
				               false, processId);
			if (hProcess != 0) {
				try {
					var pbi = new PROCESS_BASIC_INFORMATION();
					int pSize = 0;
					if (-1 != NtQueryInformationProcess(hProcess,
						    PROCESSINFOCLASS.ProcessBasicInformation, ref pbi, pbi.Size, ref
					                                    pSize)) {
						ParentID = pbi.InheritedFromUniqueProcessId;
					}
				} finally {
					CloseHandle(hProcess);
				}
			}
			return (ParentID);
		}

		public static int[] GetChildProcessIds(int parentProcessId)
		{
			var myChildren = new ArrayList();
			foreach (Process proc in Process.GetProcesses()) {
				int currentProcessId = proc.Id;
				proc.Dispose();
				if (parentProcessId == GetParentProcessId(currentProcessId)) {
					// Add this one
					myChildren.Add(currentProcessId);
					// Add any of its children
					myChildren.AddRange(GetChildProcessIds(currentProcessId));
				}

			}
			return (int[])myChildren.ToArray(typeof(int));
		}
		
		// 输出信息接收记录服务
		private static void OnDataReceived(object sender, DataReceivedEventArgs e)
		{
			string info = e.Data;
			if (String.IsNullOrEmpty(info))
				return;
			// TODO 此处添加消息接收监控
			var cont = true;
			foreach (TYPES.F_FUNC f in dataReceivers) {
				if (f != null)
					cont = (cont && f(info));
			}
			if (cont)
				Console.WriteLine(info);
		}
		
		/// <summary>
		/// 关闭指定进程
		/// </summary>
		/// <param name="procname">进程名</param>
		/// <returns>关闭信息</returns>
		public static string closeProc(string procname)
		{
			keeprun = false;
			Process[] ps = Process.GetProcessesByName(procname);
			if (ps != null && ps.Length > 0) {
				foreach (Process p in ps) {
					try {
						KillTree(p.Id);
					} catch (Exception e) {
						return "失败：" + e.Message;
					}
				}
				return "已关闭指定进程";
			}
			return "未能获取指定进程信息";
		}
		
		private static bool findedProcName(string pname)
		{
			Process[] ps = Process.GetProcessesByName(pname);
			return (ps != null && ps.Length > 0);
		}
		
		// 重启服务
		private static void startProcThread()
		{
			keeprun = true;

			myProcess = new Process();
			myProcess.StartInfo.FileName = procpath;//控制台程序的路径
			myProcess.StartInfo.Arguments = null;
			myProcess.StartInfo.UseShellExecute = false;
			myProcess.StartInfo.RedirectStandardOutput = true;
			myProcess.StartInfo.RedirectStandardInput = true;
			myProcess.StartInfo.CreateNoWindow = true;
			myProcess.StartInfo.StandardOutputEncoding = Encoding.UTF8;
			myProcess.OutputDataReceived += OnDataReceived;
			
			myProcess.Start();
			myProcess.BeginOutputReadLine();
			myProcess.WaitForExit();
			myProcess.Close();
			myProcess = null;
			if(keeprun) {
				Console.WriteLine("[BDSCSBox] bds非正常退出，开服工具盒同步关闭…");
				System.Environment.Exit(-1);
			}
		}
		
		/// <summary>
		/// 发送指定消息
		/// </summary>
		/// <param name="cmd">消息</param>
		/// <returns>消息反馈</returns>
		public static string sendCommand(string cmd)
		{
			if (myProcess != null) {
				if (!myProcess.HasExited) {
					//TODO 此处添加命令输入监控
					var cont = true;
					foreach (TYPES.F_FUNC f in dataSenders) {
						if (f != null)
							cont = cont && f(cmd);
					}
					//byte [] bcmds = Encoding.Default.GetBytes(cmd);
					//myProcess.StandardInput.BaseStream.Write(bcmds, 0, bcmds.Length);
					//myProcess.StandardInput.WriteLine("");
					if (cont)
						myProcess.StandardInput.WriteLine(cmd);
				}
			} else {
				// 远端进程发送消息
				// 出错
				return "由于进程被回收，命令发送失败。";
				//return postLongCmd(pname, cmd);
			}
			return "命令" + cmd + "已发送";
		}
		
		/// <summary>
		/// 我要开服
		/// </summary>
		/// <param name="fpath">服务端应用实际完整路径</param>
		/// <returns>开服信息</returns>
		public static string StartProc(string fpath)
		{
			if (myProcess != null) {
				if (!myProcess.HasExited)
					return "已启动一个实例";
			}
			/* 已设置为子进程，无需杀死其它进程
			if (findedProcName(pname)) {
				// 此处需要重新杀进程
				//return "已启动一个实例";
				closeProc(pname);
				Thread.Sleep(4000);	// 自动重启时限：4s
			}
			*/
			procpath = fpath;
			// 共享文件自检
//			Thread tmsg = new Thread(startProcReadMsg);
//			tmsg.Start();
			var tproc = new Thread(startProcThread);
			tproc.Start();
			return "尝试开服，请使用log查看信息";
		}

		static void loadStartInfo()		// 加载配置文件启动项
		{
			var boxconfigfilepath = System.AppDomain.CurrentDomain.BaseDirectory + BOXCONFIGFILE;
			if (File.Exists(boxconfigfilepath)) {
				var configstrs = File.ReadAllLines(boxconfigfilepath);
				if (configstrs != null) {
					foreach (var confstr in configstrs) {
						if (confstr.IndexOf('#') == 0)
							continue;
						var datas = confstr.Split('=');
						if (datas.Length > 1) {
							var k = datas[0].Trim().ToLower();
							if (k == KEY_WORKDIR){
								WORKDIR = datas[1].Trim().ToLower();
							} else if (k == KEY_PROPATH){
								BDSPRONAME = datas[1].Trim().ToLower();
							} else if (k == KEY_WEBDOWN) {
								WEBDOWN = datas[1].Trim();
							}
						}
					}
				}
			} else {
				string defconf = KEY_WORKDIR + "=" + WORKDIR + "\n" +
					KEY_PROPATH + "=" + BDSPRONAME + "\n" + KEY_WEBDOWN + "=" + WEBDOWN;
				File.WriteAllText(boxconfigfilepath, defconf);
			}
			
		}
		
		[DllImport("msvcrt.dll", SetLastError = false, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        private extern static void system(string command); // longjmp
        
		static string Httpdown(string url) {		// 下载win版bds压缩包
			ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
			var r = (HttpWebRequest)WebRequest.Create(url);
			r.Method = "GET";
			r.Timeout = 30000; // 超时时间（毫秒）
			string bdsfname = "bds.zip";
			using(var rp = r.GetResponse()){
				using(var rs = rp.GetResponseStream()){
					var fss = url.Split('/');
					if (fss.Length > 0)
						bdsfname = fss[fss.Length-1];
					using(var f = File.Create(WORKDIR + bdsfname)) {
						byte[] buffer = new byte[1024];
						int bytesRead;
						while ((bytesRead = rs.Read(buffer, 0, buffer.Length)) > 0)
						{
							f.Write(buffer, 0, bytesRead);
						}
					}
				}
			}
			string extratcmd = "tar -xf " + (WORKDIR + bdsfname) + " -C " + WORKDIR;
			system(extratcmd);
			return "OK";
		}
		
		public static void Main(string[] args)
		{
			Console.OutputEncoding = Encoding.UTF8;
			Console.InputEncoding = Encoding.Default;
			
			Console.WriteLine("[BDSCSBox] BDS开服工具盒正在启动…");
			
			Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
			
			loadStartInfo();
			
			getInstanse().loadAllLib();
			
			string bds = System.AppDomain.CurrentDomain.BaseDirectory + WORKDIR + BDSPRONAME;
			if (!File.Exists(bds)) {
				Console.WriteLine("[BDSCSBox] 未发现bds程序 " + bds + "，检查最新版本bds并下载解压。。");
				Httpdown(WEBDOWN);
				if(!File.Exists(bds))
					System.Environment.Exit(-1);
				else {
					string conf = "server.properties";
					if (File.Exists(conf))
						system("copy server.properties " + (System.AppDomain.CurrentDomain.BaseDirectory + WORKDIR) + " /y");
				}
			}
			StartProc(bds);
			
			Thread.Sleep(1000);
			if (myProcess != null && !myProcess.HasExited) {
				JobControl job = new JobControl();
				job.AddProcess(myProcess);
			} else {
				Console.WriteLine("[BDSCSBox] 未能附加bds至开服工具盒，退出应用…");
				System.Environment.Exit(-1);
			}

			do{
				string cmd = Console.ReadLine();
				if (cmd.Trim().ToLower() == "stop") {
					cmd = "stop";
					keeprun = false;
				}
				sendCommand(cmd);
			} while (keeprun);
		}
		
		private Program(){
		}
		
		public static Program getInstanse() {
			if (i_this == null)
				i_this = new Program();
			return i_this;
		}
	}

}